package cn.wen.mybatis.framework.executor.statement;


import cn.wen.mybatis.framework.executor.ErrorContext;
import cn.wen.mybatis.framework.executor.Executor;
import cn.wen.mybatis.framework.executor.ExecutorException;
import cn.wen.mybatis.framework.executor.paramete.ParameterHandler;
import cn.wen.mybatis.framework.executor.resultset.ResultSetHandler;
import cn.wen.mybatis.framework.mapping.BoundSql;
import cn.wen.mybatis.framework.mapping.MappedStatement;
import cn.wen.mybatis.framework.reflection.factory.ObjectFactory;
import cn.wen.mybatis.framework.session.Configuration;
import cn.wen.mybatis.framework.session.ResultHandler;
import cn.wen.mybatis.framework.session.RowBounds;
import cn.wen.mybatis.framework.type.TypeHandlerRegistry;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * 基类 Statement处理器
 *
 * 用于简化StatementHandler接口开发的难度，是适配器模式的体现，他有三个实现类：
 * SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler
 * 在RoutingStatementHandler创建时，就根据接受的Executor来创建这三个类型对象。
 */
public abstract class BaseStatementHandler implements StatementHandler {

  /**
   * mybatis全局配置信息
   */
  protected final Configuration configuration;
  /**
   * 对象工厂
   */
  protected final ObjectFactory objectFactory;
  /**
   * 类型处理注册器，从mybatis全局配置信息中获取
   */
  protected final TypeHandlerRegistry typeHandlerRegistry;
  /**
   * 结果集处理器
   */
  protected final ResultSetHandler resultSetHandler;

  /**
   * 参数处理器
   */
  protected final ParameterHandler parameterHandler;

  /**
   * 执行器
   */
  protected final Executor executor;
  /**
   *  Mapper.xml文件的select,delete,update,insert这些DML标签的封装类
   */
  protected final MappedStatement mappedStatement;
  /**
   *  Mybatis的分页对象
   */
  protected final RowBounds rowBounds;
  /**
   * 参数映射与可执行SQL封装类对象
   */
  protected BoundSql boundSql;

  /**
   *
   * @param executor 执行器
   * @param mappedStatement Mapper.xml文件的select,delete,update,insert这些DML标签的封装类
   * @param parameterObject 参数对象
   * @param rowBounds Mybatis的分页对象
   * @param resultHandler 结果处理器
   * @param boundSql 参数映射与可执行SQL封装类对象
   */
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) {
      //构建boundSQL
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;
    //构建参数处理器，默认情况下，构建的是DefaultParameteHandler实例
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //构建结果集处理器，构建出来的是DefaultResultSetHandler实例
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

  /**
   * 获取参数映射与可执行SQL封装类对象
   */
  @Override
  public BoundSql getBoundSql() {
    return boundSql;
  }

  /**
   * 获取参数处理器
   */
  @Override
  public ParameterHandler getParameterHandler() {
    return parameterHandler;
  }

  /**
   * 创建Statement对象或则PreparedStatment或则CallableStatment
   * @param connection 数据库连接对象
   * @param transactionTimeout 事务超时时间
   * @return 构建出来的Statement实例，已经配置了queryTimeout和fetchSize
   */
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    //设置错误日志上下文
    ErrorContext.instance().sql(boundSql.getSql());
    //定义一个 Statement
    Statement statement = null;
    try {
      //实例化 Statement
      statement = instantiateStatement(connection);
      //设置 Statement超时时长
      setStatementTimeout(statement, transactionTimeout);
      //设置 FetchSize:
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      //关闭 Statement对象
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      //关闭 Statement对象
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

  /**
   * 实例化Statement对象
   * @param connection 数据库连接
   * @return Statement对象
   */
  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

  /**
   * 设置Statement超时时长
   * @param stmt 执行SQL 语句并返回它所生成结果的对象。
   * @param transactionTimeout 事务超时时长
   */
  protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
    // 定义查询超时时长变量名
    Integer queryTimeout = null;
    // 如果有配置超时时长
    if (mappedStatement.getTimeout() != null) {
      // 应用配置的超时时长
      queryTimeout = mappedStatement.getTimeout();

      // 如果由配置全局的Statement超时时长
    } else if (configuration.getDefaultStatementTimeout() != null) {
      // 应用全局配置的Statement超时时长
      queryTimeout = configuration.getDefaultStatementTimeout();
    }
    // 如果queryTime不为null
    if (queryTimeout != null) {
      // 将queryTime设置到Statement的
      stmt.setQueryTimeout(queryTimeout);
    }
    // 应用事务超时。事务超时大于查询时间，则不做更改；否则将查询时间修改成事务时间
    StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
  }

  /**
   *
   * 设置FetchSize:
   * <p>
   *     这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset（依赖驱动）。
   * </p>
   * <br/>
   * <p>
   *     假设fetchSize为5，查询总记录数为100，每批5次返回给你，最后结果还是总记录数，只是提高点查询的速度而已
   * </p>
   * <br/>
   * <p>
   *     MySQL不支持fetchSize，默认为一次性取出所有数据。所以容易导致OOM，
   *     如果是Oracle的话就是默认取出fetchSize条数据。
   *     裸露JDBC防止OOM可以调用statement的enableStreamingResults方法,
   *     MyBatis应该在&lt;select fetchSize="-2147483648"&gt;。
   * </p>
   * @param stmt 执行SQL 语句并返回它所生成结果的对象。
   */
  protected void setFetchSize(Statement stmt) throws SQLException {
    // 获取select标签的fetchSize属性
    Integer fetchSize = mappedStatement.getFetchSize();
    // 如有设置fetchSize
    if (fetchSize != null) {
      // 设置fetchSize到stmt
      stmt.setFetchSize(fetchSize);
      return;
    }
    // 获取全局配置的FetchSize
    Integer defaultFetchSize = configuration.getDefaultFetchSize();
    // 如有设置fetchSize
    if (defaultFetchSize != null) {
      // 设置fetchSize到stmt
      stmt.setFetchSize(defaultFetchSize);
    }
  }

  /**
   * 关闭Statement对象
   * @param statement 执行SQL 语句并返回它所生成结果的对象
   */
  protected void closeStatement(Statement statement) {
    try {
      // 如果statement不为null
      if (statement != null) {
        // 关闭statement
        statement.close();
      }
    } catch (SQLException e) {
      // ignore 忽略关闭时的异常
    }
  }


}
